/*
 * Copyright (c) 2023, by xxh(name xiongjinqi)
*/

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/log2.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/of.h>
#include <linux/cdev.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>

//#define ATC24CXX_MAJOR              239
#define AT24CXX_MULTI_DEV_MAX       3
#define AT24CXX_NAME_PREFIX         "xxh_at24c"

struct at24cxx_struct {
    char name[32];
    struct cdev cdev;
    struct class *class;
    struct i2c_client *client;
};


static int major;
static int count;
struct at24cxx_struct *at24cxx;

static int at24cxx_open(struct inode *inode, struct file *filp)
{
    struct at24cxx_struct *at24;
    
    printk("%s line %d \n", __FUNCTION__, __LINE__);
    at24 = container_of(inode->i_cdev, struct at24cxx_struct, cdev);

    filp->private_data = at24->client;

    return 0;
}

static int at24cxx_release(struct inode *inode, struct file *file) 
{
    
}


/* buf = reg16 + data_buff 
*/
static int at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    u8 *user_data;
    u16 addr = 0;
    int status,ret;
    struct i2c_msg msg[2];
    struct i2c_client *client;
    

    user_data = (u8*)kzalloc(size, GFP_KERNEL);
    // todo:获取client指针
    client = file->private_data;
    ret = copy_from_user(user_data, buf, size);

    addr = (user_data[0]<<8) | user_data[1];
    msg[0].addr = client->addr;
    msg[0].flags = 0;
    msg[0].buf = user_data;
    msg[0].len = size;

    printk("%s line %d: client addr [%02x], write addr [%d], write data [%d] \n", \
            __FUNCTION__, __LINE__, client->addr, addr, user_data[2]);

    status = i2c_transfer(client->adapter, msg, 1);
    kfree(user_data);
    mdelay(20);
    
    return size-2;
}


static int at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
    u8 user_data[3], rdata = 0;
    u16 read_addr = 0;
    int status, ret;
    struct i2c_msg msg[2];
    struct i2c_client *client;
    
    printk("%s line %d \n", __FUNCTION__, __LINE__);
    // todo:获取client指针
    client = file->private_data;
    ret = copy_from_user(user_data, buf, 3);

    read_addr = (user_data[0]<<8) | user_data[1];
    msg[0].addr = client->addr;
    msg[0].flags = 0;
    msg[0].buf = user_data;
    msg[0].len = 2;

    msg[1].addr = client->addr;
    msg[1].flags = I2C_M_RD;
    msg[1].buf = &rdata;
    msg[1].len = 1;


    status = i2c_transfer(client->adapter, msg, 2);

    printk("%s line %d: client addr [%02x], read addr [%d], data [%02x] \n", \
            __FUNCTION__, __LINE__, client->addr, read_addr, rdata);

    ret = copy_to_user(buf+2, &rdata, 1);

    return 1;
}


static struct file_operations at24cxx_fops = {
    .owner = THIS_MODULE,
    .open  = at24cxx_open,
    .read = at24cxx_read,
    .write = at24cxx_write,
};


static int at24cxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    printk("%s line %d: get client address [%#x], count = %d \n", __FUNCTION__, __LINE__, client->addr,count);
    at24cxx[count++].client = client;
    return 0;
}

static int at24cxx_remove(struct i2c_client *client)
{
    int index = 0;
    printk("%s line %d \n", __FUNCTION__, __LINE__);

    return 0;
}


static struct of_device_id at24cxx_of_match[] = {
    {.compatible = "xxh,at24cxx",},
    {}
};


static struct i2c_device_id at24cxx_ids[] = {
    {.name = "at24cxx", (kernel_ulong_t)NULL},
    {}
};


static struct i2c_driver at24cxx_driver = {
    .driver = {
        .name = "at24cxx",
        .of_match_table = at24cxx_of_match,
    },
    .probe = at24cxx_probe,
    .remove = at24cxx_remove,
    .id_table = at24cxx_ids,
};

/*初始化并注册cdev*/
static void at24cxx_setup_cdev(struct at24cxx_struct *dev, int index)
{
    int err;
    dev_t devno = MKDEV(major, index);

    //dev->cdev.owner = THIS_MODULE;
    cdev_init(&dev->cdev, &at24cxx_fops);
    err = cdev_add(&dev->cdev, devno, 1);
    if (err)
    {
        printk(KERN_NOTICE "Error %d adding AT24C%d", err, index);
        unregister_chrdev_region(devno, 1);
    }
}


static int __init at24cxx_init(void)
{
    int ret = 0, index;
    dev_t at24cxx_devid;
    
    printk("%s line %d \n", __FUNCTION__, __LINE__);

    if(alloc_chrdev_region(&at24cxx_devid, 0, AT24CXX_MULTI_DEV_MAX, "xxh_at24c") < 0)
    {
        printk(KERN_ERR"Unable to alloc_chrdev_region.\n");
        return -EINVAL;
    } 
    major = MAJOR(at24cxx_devid);
    printk("%s line %d: major [%d] \n", __FUNCTION__, __LINE__, major);
    
    /* 分配at24cxx结构体 */
    at24cxx = kmalloc(sizeof(struct at24cxx_struct)*AT24CXX_MULTI_DEV_MAX, GFP_KERNEL);
    memset(at24cxx, 0, sizeof(struct at24cxx_struct)*AT24CXX_MULTI_DEV_MAX);
    
    if(!at24cxx) // 申请失败 
    {
        printk("%s line %d: alloc err!!! \n", __FUNCTION__, __LINE__);
        ret = -ENOMEM;
        goto FAIL;
    }
    at24cxx_setup_cdev(&at24cxx[0], 0);
    at24cxx_setup_cdev(&at24cxx[1], 1);
    at24cxx_setup_cdev(&at24cxx[2], 2);

    for(index=0; index < AT24CXX_MULTI_DEV_MAX; index++)
    {
        sprintf(at24cxx[index].name, "%s%02x", AT24CXX_NAME_PREFIX, index);
        printk("%s line %d: creat class name %s \n", __FUNCTION__, __LINE__, at24cxx[index].name);
        
        at24cxx[index].class = class_create(THIS_MODULE, at24cxx[index].name);  /* /sys/class/xxhds18b20 */
        device_create(at24cxx[index].class, NULL, MKDEV(major, index), NULL, at24cxx[index].name); /* /dev/xxhds18b20 */
    }
    
    i2c_add_driver(&at24cxx_driver);
    
FAIL:
    return ret;
}

static void __exit at24cxx_exit(void)
{
    int index;
    printk("%s line %d \n", __FUNCTION__, __LINE__);
    for(index=0; index < AT24CXX_MULTI_DEV_MAX; index++)
    {
        device_destroy(at24cxx[index].class, MKDEV(major, index)); /* /dev/xxhds18b20 */
        class_destroy(at24cxx[index].class);  /* /sys/class/xxhds18b20 */
    }
    unregister_chrdev_region(MKDEV(major, 0), AT24CXX_MULTI_DEV_MAX);
    cdev_del(&(at24cxx[0].cdev));
    cdev_del(&(at24cxx[1].cdev));
    cdev_del(&(at24cxx[2].cdev));
    
    kfree(at24cxx);     // 释放内存
    
    i2c_del_driver(&at24cxx_driver);
}

module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_DESCRIPTION("Driver for multi I2C EEPROM");
MODULE_AUTHOR("Xiongjinqi");
MODULE_LICENSE("GPL");
